package network

import (
	"encoding/json"
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netns"
	"io/fs"
	"net"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"text/tabwriter"
)
import "github.com/vishvananda/netlink"

var (
	defaultNetworkPath = "/var/lib/ycontainer/network/network"
	drivers            = map[string]Driver{}
	networks           = map[string]*Network{}
)

type Network struct {
	Name string
	*net.IPNet
	Driver string
}

type EndPoint struct {
	ID          string
	Device      *netlink.Veth
	IPAddress   net.IP
	MacAddress  net.HardwareAddr
	PortMapping []string
	Network     *Network
}

func (nw *Network) dump(dumpPath string) error {
	if _, err := os.Stat(dumpPath); err != nil {
		os.MkdirAll(dumpPath, 0644)
	}

	nwPath := path.Join(dumpPath, nw.Name)
	file, err := os.OpenFile(nwPath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		logrus.Errorf("error: %v", err)
		return err
	}
	defer file.Close()

	json, _ := json.Marshal(nw)
	file.Write(json)
	return nil
}

func (nw *Network) load(dumpPath string) error {
	file, _ := os.Open(dumpPath)
	defer file.Close()

	bytes := make([]byte, 2000)
	n, _ := file.Read(bytes)

	json.Unmarshal(bytes[:n], nw)
	return nil
}

func (nw *Network) remove(dumpPath string) error {
	if _, err := os.Stat(path.Join(dumpPath, nw.Name)); err != nil {
		if os.IsNotExist(err) {
			return nil
		} else {
			return err
		}
	} else {
		return os.Remove(path.Join(dumpPath, nw.Name))
	}
}

func DeleteNetwork(names []string) error {
	for _, name := range names {
		if network, ok := networks[name]; ok {
			ipam.Release(network.IPNet, &network.IPNet.IP)
			drivers[network.Driver].Delete(network)
			network.remove(defaultNetworkPath)
		}
	}
	return nil
}
func ListNetwork() {
	writer := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
	fmt.Fprint(writer, "NAME\tDriver\tIPNet\n")
	for _, item := range networks {
		fmt.Fprintf(writer, "%s\t%s\t%s\n",
			item.Name,
			item.Driver,
			item.IPNet,
		)
	}
	if err := writer.Flush(); err != nil {
		logrus.Errorf("Flush error %v", err)
	}
}

func CreateNetwork(driver, subnet, name string) error {
	_, cidr, _ := net.ParseCIDR(subnet)
	logrus.Infof("cidr: %v", cidr)

	gatewayIp, err := ipam.Allocate(cidr)

	logrus.Infof("gatewayIp: %v", gatewayIp)
	if err != nil {
		return err
	}

	cidr.IP = gatewayIp

	nw, err := drivers[driver].Create(cidr.String(), name)

	if err != nil {
		return err
	}
	return nw.dump(defaultNetworkPath)
}

func Connect(networkName, containerId string, containerPid int) error {
	network := networks[networkName]

	ip, _ := ipam.Allocate(network.IPNet)
	endPoint := &EndPoint{
		ID:          fmt.Sprintf("%s-%s", containerId, networkName),
		IPAddress:   ip,
		PortMapping: nil,
		Network:     network,
	}

	drivers[network.Driver].Connect(network, endPoint)

	configEndpointIpAddressAndRoute(endPoint, containerPid)
	configPortMapping(endPoint)
	return nil
}

func configPortMapping(endPoint *EndPoint) {
	for _, pm := range endPoint.PortMapping {
		portMapping := strings.Split(pm, ":")
		if len(portMapping) != 2 {
			logrus.Errorf("port mapping format error, %v", pm)
			continue
		}
		iptablesCmd := fmt.Sprintf("-t nat -A PREROUTING -p tcp -m tcp --dport %s -j DNAT --to-destination %s:%s",
			portMapping[0], endPoint.IPAddress.String(), portMapping[1])
		cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
		//err := cmd.Run()
		output, err := cmd.Output()
		if err != nil {
			logrus.Errorf("iptables Output, %v", output)
			continue
		}
	}
}

func configEndpointIpAddressAndRoute(endPoint *EndPoint, containerPid int) {
	peerLink, err := netlink.LinkByName(endPoint.Device.PeerName)
	if err != nil {
		logrus.Infof("err: %v", err)
	}
	defer enterContainerNetNamespace(&peerLink, containerPid)()

	endPoint.Network.IPNet.IP = endPoint.IPAddress
	setIP(endPoint.Device.PeerName, endPoint.Network.IPNet.String())

	setUP(endPoint.Device.PeerName)
	setUP("lo")

	_, ipNet, _ := net.ParseCIDR("0.0.0.0/0")

	defaultRoute := &netlink.Route{
		LinkIndex: peerLink.Attrs().Index,
		Gw:        endPoint.Network.IPNet.IP,
		Dst:       ipNet,
	}
	netlink.RouteAdd(defaultRoute)
}

func enterContainerNetNamespace(link *netlink.Link, containerPid int) func() {
	f, err := os.OpenFile(fmt.Sprintf("/proc/%d/ns/net", containerPid), os.O_RDONLY, 0)
	if err != nil {
		logrus.Errorf("error get container net namespace, %v", err)
	}

	fd := f.Fd()
	runtime.LockOSThread()

	if err = netlink.LinkSetNsFd(*link, int(fd)); err != nil {
		logrus.Errorf("error set link netns , %v", err)
	}

	origns, err := netns.Get()
	if err != nil {
		logrus.Errorf("error get current netns, %v", err)
	}

	if err = netns.Set(netns.NsHandle(fd)); err != nil {
		logrus.Errorf("error set netns, %v", err)
	}
	return func() {
		netns.Set(origns)
		origns.Close()
		runtime.UnlockOSThread()
		f.Close()
	}
}

func InitDriverAndNetwork() {
	driver := &BridgeDriver{}
	drivers[driver.Name()] = driver

	if _, err := os.Stat(defaultNetworkPath); err != nil {
		os.MkdirAll(defaultNetworkPath, 0644)
	}

	filepath.Walk(defaultNetworkPath, func(nwPath string, info fs.FileInfo, err error) error {
		//logrus.Infof("nwPath: %v", nwPath)
		if info.IsDir() {
			return nil
		}

		_, nwName := path.Split(nwPath)
		//logrus.Infof("nwName: %v", nwName)
		nw := &Network{
			Name: nwName,
		}

		if err = nw.load(nwPath); err != nil {
			logrus.Errorf("err: %v", err)
			return err
		}

		networks[nwName] = nw
		return nil
	})
}
